<?php
namespace Core;

use PDO, PDOException;

/**
 * 此类具有数据库操作的基础功能，通常被 DbTable 类继承，且 DbTable 类以ORM方法实现了数据表映射，但此类也允许被其它类继承，以应对更复杂的应用环境。
 *
 * 此类目前仅支持 MySQL 和 SQLite 两个数据库驱动。
 *
 * 数据库连接相关认识：
 * - 长连接无法被关闭，请求结束后被自动保存于连接池中，等待被下一个请求使用。
 * - 非长连接在请求处理过程中被手工关闭或在请求结束后被自动关闭，而每个新请求又会创建新的连接。
 * - 无论是长连接或是非长连接，一个连接只可能同时被一个请求使用，当同时有多个请求时，将创建多个连接，且各请求结束后，所有长连接均被保存于连接池中，而非长连接被关闭。
 *
 * 若在同一个请求内多次使用同样的连接信息与MySQL建立非长连接时：
 * - mysql_connect() 默认的返回同一个连接，但当第四个参数为 true 时，将总是创建新的连接。
 * - PDO 默认的总是创建新的连接，但可指定 PDO::ATTR_PERSISTENT 连接参数，建立或设置连接为长连接。
 *
 */
abstract class DbHandle {
	const TRANS_FLAG_BEGIN = 1;
	const TRANS_FLAG_ROLLBACK = 2;
	const TRANS_FLAG_COMMIT = 3;
	
	/**
	 * 当最近一次成功执行的SQL语句为添加、删除或更新操作时，该属性的值为受影响的记录数，否则总为0。
	 * @var int
	 * @access public
	 */
	public $affect = 0;
	
	/**
	 * 使用的数据源名称，即数据库操作句柄标识。
	 * @var string
	 * @access protected
	 */
	protected $_dsn = '';
	/**
	 * 数据缓存源名称。
	 * @var string
	 * @access protected
	 */
	protected $_mcsn = '';
	/**
	 * MC 键名前缀。
	 * @var string
	 * @access protected
	 */
	protected $_prefix = '';
	
	/**
	 * 将产生结果集的SQL命令。
	 * @var array
	 * @access private
	 */
	private $_commands = ['SELECT', 'DESCRIBE', 'DESC', 'SHOW'];
	/**
	 * MC 操作句柄。
	 * @var MCache\Redis
	 * @access private
	 */
	private $_Mcache = NULL;
	
	/**
	 * 数据库操作句柄列表。
	 * @var array
	 * @access static private
	 */
	private static $_handle = [];

	/**
	 * 初始化数据库操作
	 *
	 * 注意：
	 *
	 * 若子类中存在 __construct() 方法，则必须在子类的 __construct() 方法中以 parent::__construct() 方式调用此方法
	 */
	protected function __construct() {
		$this->_dsn || reset(HHO::$config['dsn']) && $this->_dsn = key(HHO::$config['dsn']);
	}

	/**
	 * 设置（或添加）一条内存数据缓存。
	 * 
	 * @param string $key
	 * @param mixed $value
	 * @param int $ttl
	 * @return boolean
	 */
	protected function _setMc($key, $value, $ttl) {
		if ($ttl <= 0) {
			trigger_error('Invalid TTL', E_USER_WARNING);
			return FALSE;
		}
		
		$this->_Mcache || $this->_mcsn && $this->_Mcache = MCache::getInstance($this->_mcsn);
		
		return $this->_Mcache ? $this->_Mcache->set($this->_prefix . $key, $value, $ttl) : FALSE;
	}

	/**
	 * 获取一条内存数据缓存。
	 * 
	 * @param string $key
	 * @return mixed|NULL
	 */
	protected function _getMc($key) {
		$this->_Mcache || $this->_mcsn && $this->_Mcache = MCache::getInstance($this->_mcsn);
		
		return $this->_Mcache ? $this->_Mcache->get($this->_prefix . $key) : NULL;
	}

	/**
	 * 删除一条内存数据缓存。
	 * 
	 * @param string $key
	 * @return boolean
	 */
	protected function _delMc($key) {
		$this->_Mcache || $this->_mcsn && $this->_Mcache = MCache::getInstance($this->_mcsn);
		
		return $this->_Mcache ? $this->_Mcache->del($this->_prefix . $key) : FALSE;
	}

	/**
	 * 连接至数据库，返回一个PDO对象
	 *
	 * @return PDO
	 */
	private function _connect() {
		if (!isset(self::$_handle[$this->_dsn])) {
			$parameter = &HHO::$config['dsn'][$this->_dsn];
			
			$option = array(PDO::ATTR_PERSISTENT => TRUE);
			
			isset($parameter[4]) && $parameter[4] && $option[PDO::ATTR_TIMEOUT] = $parameter[4];
			
			try {
				self::$_handle[$this->_dsn] = new PDO($parameter[0], isset($parameter[1]) ? $parameter[1] : NULL, isset($parameter[2]) ? $parameter[2] : NULL, $option);
			} catch (PDOException $e) {
				trigger_error("Database connection failed. (DSN: $this->_dsn, Message: " . trim($e->getMessage()) . ')', E_USER_WARNING);
				
				return NULL;
			}
			
			self::$_handle[$this->_dsn]->getAttribute(PDO::ATTR_DRIVER_NAME) == 'mysql' && $parameter[3] && self::$_handle[$this->_dsn]->exec("SET NAMES {$parameter[3]}");
		}
		
		return self::$_handle[$this->_dsn];
	}

	/**
	 * 对指定内容进行引起操作。
	 *
	 * @param string|int|float|array|object $value
	 * @return mixed
	 */
	protected function _quote($value) {
		$handle = $this->_connect();
		
		if (isset($handle)) {
			if (is_scalar($value)) {
				$value = $handle->quote($value);
			} else {
				foreach ($value as &$v) {
					$v = $handle->quote($v);
				}
			}
		} else {
			trigger_error("No database connection. (DSN: $this->_dsn)", E_USER_WARNING);
		}
		
		return $value;
	}

	/**
	 * 执行一条SQL，若执行SQL产生一个结果集，则返回该结果集对象，否则返回 boolean
	 *
	 * @param string $query
	 * @return DbResult|boolean
	 */
	protected function _execute($query) {
		$handle = $this->_connect();
		if (!isset($handle)) {
			return false;
		}
		
		$result = $handle->query($query);
		
		if ($handle->errorCode() != '00000') {
			$info = $handle->errorInfo();
			
			trigger_error("SQL statement execution failed. (Statement: $query, Message: " . end($info) . ')', E_USER_WARNING);
			
			return false;
		}
		
		preg_match('/^(\w+).*/', $query, $commands);
		
		if (array_search(strtoupper($commands[1]), $this->_commands) !== false) {
			$this->affect = 0;
			
			return new DbResult($result);
		}
		
		$this->affect = $result->rowCount();
		
		return true;
	}

	/**
	 * 获得最近一次插入的记录ID，注意：因为PHP整型仅支持 INT，因此若数据表的递增字段数据类型为 BIGINT 时将存在隐患
	 *
	 * @return int
	 */
	public function lastid() {
		if (isset(self::$_handle[$this->_dsn])) {
			return (int) self::$_handle[$this->_dsn]->lastInsertId();
		}
		
		return 0;
	}

	/**
	 * 获得当前数据库操作句柄的事务开启状态
	 *
	 * @return boolean
	 */
	public function getTransaction() {
		return isset(self::$_handle[$this->_dsn]) && self::$_handle[$this->_dsn]->inTransaction();
	}

	/**
	 * 管理当前数据库操作句柄的事务处理
	 *
	 * @param int $flag
	 * @return boolean
	 */
	private function _transaction($flag) {
		$handle = $this->_connect();
		
		if (isset($handle)) {
			try {
				switch ($flag) {
					case self::TRANS_FLAG_BEGIN :
						if (!$handle->beginTransaction()) {
							return FALSE;
						}
						break;
					case self::TRANS_FLAG_ROLLBACK :
						if (!$handle->rollBack()) {
							return FALSE;
						}
						break;
					case self::TRANS_FLAG_COMMIT :
						if (!$handle->commit()) {
							return FALSE;
						}
						break;
					default :
						return FALSE;
				}
				
				return TRUE;
			} catch (PDOException $e) {
				trigger_error("Database connection failed. (DSN: $this->_dsn, Message: " . trim($e->getMessage()) . ')', E_USER_WARNING);
			}
		}
		
		return FALSE;
	}

	/**
	 * 开启事务
	 *
	 * @return boolean
	 */
	public function begin() {
		return $this->_transaction(self::TRANS_FLAG_BEGIN);
	}

	/**
	 * 回滚事务
	 *
	 * @return boolean
	 */
	public function rollback() {
		return $this->_transaction(self::TRANS_FLAG_ROLLBACK);
	}

	/**
	 * 提交事务
	 *
	 * @return boolean
	 */
	public function commit() {
		return $this->_transaction(self::TRANS_FLAG_COMMIT);
	}

	/**
	 * 做数据库后续处理，比如 SQLite 数据操作结束后，执行 VACUUM 命令整理数据库
	 *
	 * @return void
	 */
	public function __destruct() {
		if (isset(self::$_handle[$this->_dsn])) {
			self::$_handle[$this->_dsn]->getAttribute(PDO::ATTR_DRIVER_NAME) == 'sqlite' && in_array(mt_rand(0, 9), [0, 5]) && self::$_handle[$this->_dsn]->exec('VACUUM');
		}
	}

	/**
	 * 单实例模式，若需要实现多实例，可在子类中重写此方法，此方法也已为子类实现了单实例模式
	 *
	 * @return Object
	 */
	static public function getInstance() {
		static $instance = array();
		
		$class = get_called_class();
		
		if (!isset($instance[$class])) {
			$instance[$class] = new static();
		}
		
		return $instance[$class];
	}
}